﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using Demos.Problems;
using Image = System.Windows.Controls.Image;

namespace Demos.Sorting.Quicksort
{
    class ViewModel:INotifyPropertyChanged
    {
        const int anchuraImagen = 80;
        const int _duracion = 300;
        const int _numMovimientos = 15;

        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        ObservableQuickSort.InPlaceQuickSorter _model;
        private List<Algoritmia.Problems.Sorting.ISorter<int>> sorteds;
        private List<Algoritmia.Problems.Sorting.IInPlaceSorter<int>> inPlaces;
        static private Random _r;
        private List<KeyValuePair<int, int>> _movimientos;
        public event AnimarBaraja Animar;
        public delegate void PruebaHandler(double tiempoAcumulado, int algoritmo);
        public event PruebaHandler PruebaFinalizada;

        private List<int> _baraja = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
        public ObservableCollection<UIElement> Baraja { get; set; }

        public string[] Resultados { get; private set; }
        public bool[] OpcionAlgoritmo { get; private set; }

        private int _talla;
        public int Talla
        {
            get { return _talla; }
            set
            {
                _talla = value;
                NotifyPropertyChanged("Talla");
                NotifyPropertyChanged("TallaView");
            }
        }

        public string TallaView
        {
            get { return Talla.ToString(); }
            set
            {
                try
                {
                    Talla = int.Parse(value);
                }
                catch (FormatException)
                {
                    MessageBox.Show("Error de formato", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
        }

        private bool _animando;

        public ICommand DesordenarCommand { get; private set; }
        public ICommand OrdenarCommand { get; private set; }
        public ICommand ExperimentarCommand { get; private set; }


        public ViewModel(ObservableQuickSort.InPlaceQuickSorter model)
        {
            _animando = false;
            Talla = 0;
            _model = model;
            _r = new Random(DateTime.Now.Millisecond);
            _movimientos = new List<KeyValuePair<int, int>>();
            sorteds = new List<Algoritmia.Problems.Sorting.ISorter<int>>();
            inPlaces = new List<Algoritmia.Problems.Sorting.IInPlaceSorter<int>>();

            sorteds.Add(new Algoritmia.Problems.Sorting.BasicQuickSorter<int>());
            inPlaces.Add(new Algoritmia.Problems.Sorting.InPlaceQuickSorter<int>());
            inPlaces.Add(new Algoritmia.Problems.Sorting.BasicInPlaceQuickSorter<int>());
            inPlaces.Add(new Algoritmia.Problems.Sorting.RandomizedInPlaceQuickSorter<int>());
            inPlaces.Add(new Algoritmia.Problems.Sorting.BasicSemiIterativeInPlaceQuickSorter<int>());
            inPlaces.Add(new Algoritmia.Problems.Sorting.SemiIterativeInPlaceQuickSorter<int>());
            inPlaces.Add(new Algoritmia.Problems.Sorting.SemiIterativeInPlaceQuickSorter1<int>());
            inPlaces.Add(new Algoritmia.Problems.Sorting.RandomizedSemiIterativeInPlaceQuickSorter<int>());

            CargarBaraja();

            Resultados = new string[8];
            OpcionAlgoritmo = new bool[8];

            Animar += Mover;
            PruebaFinalizada += PruebaPublicador;

            DesordenarCommand = new RelayCommand(Desordenar, () => !_animando);
            OrdenarCommand = new RelayCommand(Solucionar, PuedoSolucionar);
            ExperimentarCommand = new RelayCommand(Experimentar, () => true);
        }


        private void CargarBaraja()
        {
            Baraja = new ObservableCollection<UIElement>();
            for (var i = 1; i < 11; i++)
            {
                try
                {
                    var carta = new Image();
                    var x = (Bitmap)System.Drawing.Image.FromFile(@"media\quicksort\o" + i + ".jpg");
                    var ms = new MemoryStream();
                    x.Save(ms, ImageFormat.Png);
                    ms.Position = 0;
                    var bi = new BitmapImage();
                    bi.BeginInit();
                    bi.StreamSource = ms;
                    bi.EndInit();
                    carta.Source = bi;
                    Canvas.SetLeft(carta, anchuraImagen * (i - 1));
                    carta.Width = 75;
                    Baraja.Add(carta);
                }
                catch (Exception)
                {
                    MessageBox.Show("Error al cargar baraja", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
        }

        private static void RandomMove(ref int carta1, ref int carta2)
        {
            carta1 = _r.Next(0, 10);
            carta2 = _r.Next(0, 10);
        }

        private void Desordenar()
        {
            for (var i = 0; i < _numMovimientos; i++)
            {
                int carta1 = 0, carta2 = 0;
                RandomMove(ref carta1, ref carta2);
                AddMove(carta1, carta2);
            }
            _animando = true;
            Animar(this, new EventArgs());
        }

        private bool PuedoSolucionar()
        {
            if (!_animando)
            {
                for (int i = 0; i < 10; i++)
                {
                    if (_baraja[i] != i)
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        private void Solucionar()
        {
            _model.AñadirMovimiento += AddMove;
            _model.ListaOrdenada += Animar;
            _animando = true;
            _baraja = _model.Sort(_baraja);
        }

        private void AddMove(int carta1, int carta2)
        {
            _movimientos.Add(new KeyValuePair<int, int>(carta1, carta2));
        }

        private KeyValuePair<int,int> NextMove()
        {
            if (_movimientos.Count != 0)
            {
                var move = _movimientos[0];
                _movimientos.RemoveAt(0);
                return move;
            }
            return new KeyValuePair<int, int>(-1,-1);
        }

        private void Experimentar()
        {
            for (var indice = 0; indice < OpcionAlgoritmo.Count(); indice++)
            {
                if (OpcionAlgoritmo[indice])
                {
                    var desordenada = new Algoritmia.DataStructures.Lists.ArrayList<int>();
                    for (var j = 0; j < Talla; j++)
                    {
                        desordenada.Add(_r.Next());
                    }

                    if (indice < sorteds.Count)
                    {
                        ExperimentarSorted(indice, desordenada);
                    }
                    else
                    {
                        ExperimentarInPlace(indice, desordenada);
                    }    
                }
            }
        }

        private void ExperimentarSorted(int indice, Algoritmia.DataStructures.Lists.ArrayList<int> desordenada)
        {
            var mainDispatcher = Dispatcher.CurrentDispatcher;
            var indice1 = indice;
            var prueba = new Task(() =>
            {
                var tiempo1 = DateTime.Now;
                sorteds[indice1].Sorted(desordenada);
                var tiempo2 = DateTime.Now;
                var tiempoAcumulado = (tiempo2 - tiempo1).TotalMilliseconds;

                mainDispatcher.BeginInvoke(DispatcherPriority.Normal, new ThreadStart(
                    () => PruebaFinalizada(tiempoAcumulado, indice1)));
                return;
            });
            prueba.Start();
        }

        private void ExperimentarInPlace(int indice, Algoritmia.DataStructures.Lists.ArrayList<int> desordenada)
        {
            var mainDispatcher = Dispatcher.CurrentDispatcher;
            var indice1 = indice - sorteds.Count;
            var indice2 = indice;
            var prueba = new Task(() =>
            {
                var tiempo1 = DateTime.Now;
                inPlaces[indice1].Sort(desordenada, Comparer<int>.Default.Compare);
                var tiempo2 = DateTime.Now;
                var tiempoAcumulado = (tiempo2 - tiempo1).TotalMilliseconds;

                mainDispatcher.BeginInvoke(DispatcherPriority.Normal, new ThreadStart(
                    () => PruebaFinalizada(tiempoAcumulado, indice2)));

                return;
            });
            prueba.Start();
        }

        private void Mover(object sender, EventArgs e)
        {
            var mov = NextMove();
            if (mov.Key != -1 && mov.Value != -1)
            {
                var carta1 = Baraja[_baraja[mov.Key]];
                var carta2 = Baraja[_baraja[mov.Value]];
                ReordenarCartas(mov);

                var sb1 = new Storyboard();
                var duration1 = new Duration(TimeSpan.FromMilliseconds(_duracion));
                var myDoubleAnimation1 = new DoubleAnimation();
                var myDoubleAnimation2 = new DoubleAnimation();
                myDoubleAnimation1.Duration = duration1;
                myDoubleAnimation2.Duration = duration1;
                sb1.Duration = duration1;
                sb1.Children.Add(myDoubleAnimation1);
                sb1.Children.Add(myDoubleAnimation2);

                Storyboard.SetTargetProperty(myDoubleAnimation1, new PropertyPath("(Canvas.Left)"));
                Storyboard.SetTargetProperty(myDoubleAnimation2, new PropertyPath("(Canvas.Left)"));
                Storyboard.SetTarget(myDoubleAnimation1, carta1);
                Storyboard.SetTarget(myDoubleAnimation2, carta2);

                myDoubleAnimation1.From = Canvas.GetLeft(carta1);
                myDoubleAnimation1.To = Canvas.GetLeft(carta2);
                myDoubleAnimation2.From = Canvas.GetLeft(carta2);
                myDoubleAnimation2.To = Canvas.GetLeft(carta1);
                sb1.Completed += Mover;
                sb1.Begin();
            }
            else
            {
                _animando = false;
            }
        }

        private void ReordenarCartas(KeyValuePair<int, int> ultimoMovimiento)
        {
            var aux = _baraja[ultimoMovimiento.Key];
            _baraja[ultimoMovimiento.Key] = _baraja[ultimoMovimiento.Value];
            _baraja[ultimoMovimiento.Value] = aux;
        }

        void PruebaPublicador(double tiempoAcumulado, int algoritmo)
        {
            Resultados[algoritmo] = string.Format("Tiempo total: {0} ms", tiempoAcumulado);
            NotifyPropertyChanged("Resultados");
        }
    }
}
